---------
-- ## HTTP工具类
--
-- Dependencies: `extension`
-- @module ApmHttpUtil
-- @author songbb
-- @copyright All Rights Reserved Copyright (C) 2014 - 2017 broada,Co.,Ltd.

require("extension")

ApmHttpUtil = {}

local function decode(str)
    local str = str:gsub('+', ' ')
    return (str:gsub("%%(%x%x)", function(c)
        return string.char(tonumber(c, 16))
    end))
end

--- 解析URL参数。
--
-- 将形如
--    foo=1&p[]=2&p[]=3&bar=4&time['now']=06
-- 的参数组织成形如
--    {
--        ["bar"] = "4",
--        ["foo"] = "1",
--        ["p"] = {
--            "2",
--            "3",
--        },
--        ["time"] = {
--            ["'now'"] = "06",
--        },
--    }
-- 的table返回
-- @string str URL参数字符串
-- @treturn table 重新组织后的URL参数
function ApmHttpUtil.parseParams(str)
    local values = {}
    local len = str:len()
    local i_loop = 1
    while i_loop <= len
    do
        local s_kv, key, val
        local i = str:find('&', i_loop)
        if i ~= nil then
            s_kv = str:sub(i_loop, i - 1)
            i_loop = i + 1
        else
            s_kv = str:sub(i_loop)
            i_loop = len + 1
        end

        if s_kv == nil then break end
        local i_and = s_kv:find('=')
        if i_and ~= nil then
            key = s_kv:sub(1, i_and - 1)
            val = s_kv:sub(i_and + 1)
        else
            key = s_kv
            val = ""
        end

        key = decode(key)
        local keys = {}
        key = key:gsub('%[([^%]]*)%]', function(v)
            -- extract keys between balanced brackets
            if string.find(v, "^-?%d+$") then
                v = tonumber(v)
            else
                v = decode(v)
            end
            table.insert(keys, v)
            return "="
        end)
        key = key:gsub('=+.*$', "")
        key = key:gsub('%s', "_") -- remove spaces in parameter name
        val = val:gsub('^=+', "")

        if not values[key] then
            values[key] = {}
        end
        if #keys > 0 and type(values[key]) ~= 'table' then
            values[key] = {}
        elseif #keys == 0 and type(values[key]) == 'table' then
            values[key] = decode(val)
        end

        local t = values[key]
        for i,k in ipairs(keys) do
            if type(t) ~= 'table' then
                t = {}
            end
            if k == "" then
                k = #t+1
            end
            if not t[k] then
                t[k] = {}
            end
            if i == #keys then
                t[k] = decode(val)
            end
            t = t[k]
        end
    end
    return values
end

--- 从ApmData中提取Http协议GET方法和POST方法的参数，返回一个table。
-- @tparam ApmData ApmData 上报数据操作类
-- @treturn table 参数
function ApmHttpUtil.getParams(ApmData)
    if ApmData == nil then return nil end
    -- 必须是HTTP协议
    if ApmData.getL7Proto() ~= 80 then return nil end
    -- 必须是请求或者响应
    local i_MsgType = ApmData.getMsgType()
    if i_MsgType ~= 0 and i_MsgType ~= 1 then return nil end

    local t = {}
    local s_method = ApmData.getField("request.http.method", 1)
    if s_method == nil then return nil end

    if s_method == "GET" then
        local s_param = ApmData.getField("request.http.param.data", 1)
        if s_param == nil or s_param == "" then return nil end
        return ApmHttpUtil.parseParams(s_param)
    elseif s_method == "POST" then
        local i_multipart_size = ApmData.getField("request.http.multipart.size", 1)
        if i_multipart_size ~= nil and i_multipart_size ~= "" and type(i_multipart_size) == 'string' then
            i_multipart_size = tonumber(i_multipart_size)
            if i_multipart_size > 0 then
                local s_body, t_tmp
                for i=1, i_multipart_size do
                    s_body = ApmData.getField("request.http.body." .. i, 1)
                    if s_body ~= nil and s_body ~= "" then
                        t_tmp = ApmHttpUtil.parseParams(s_body)
                        t = table.merge(t, t_tmp)
                    end
                end
                return t
            end
        else
            local s_body = ApmData.getField("request.http.body.1", 1)
            if s_body == nil or s_body == "" then return nil end
            return ApmHttpUtil.parseParams(s_body)
        end
    end
    return nil
end

--- 从ApmData中提取HTTP协议中的URL参数。
-- @tparam ApmData ApmData 上报数据操作类
-- @treturn table URL参数
function ApmHttpUtil.getURLParams(ApmData)
    if ApmData == nil then return nil end
    -- 必须是HTTP协议
    if ApmData.getL7Proto() ~= 80 then return nil end
    -- 必须是请求或者响应
    local i_MsgType = ApmData.getMsgType()
    if i_MsgType ~= 0 and i_MsgType ~= 1 then return nil end

    local s_params = ApmData.getField("request.http.param.data", 1)
    if s_params == nil or s_params == "" then return nil end
    return ApmHttpUtil.parseParams(s_params)
end

return ApmHttpUtil
